# 前言
突然觉得对于一名JavaScript开发者而言,需要知道JavaScript程序内部是如何运行的,那么对于此章节执行上下文和执行栈的理解很重要,对理解其他JavaScript概念(变量声明提示,作用域和闭包)都有帮助。
看了很多相关文章,写得很好,总结了ES3以及ES6对于执行上下文概念的描述,以及新的概念介绍。
# 什么是执行上下文
简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。
# 执行上下文的类型
JavaScript 中有三种执行上下文类型
- 全局执行上下文 — 这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置
this的值等于这个全局对象。一个程序中只会有一个全局执行上下文。 - 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
- Eval 函数执行上下文 — 执行在
eval函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用eval,所以在这里我不会讨论它。
# ES3 执行上下文的内容
执行上下文是一个抽象的概念,我们可以将它理解为一个 object ,一个执行上下文里包括以下内容:
- 变量对象 VO
- 活动对象 AO
- 作用域链
- 调用者信息 this
# 变量对象(variable object 简称 VO)
每个执行环境文都有一个表示变量的对象——变量对象,全局执行环境的变量对象始终存在,而函数这样局部环境的变量,只会在函数执行的过程中存在,在函数被调用时且在具体的函数代码运行之前,JS 引擎会用当前函数的参数列表(arguments)初始化一个 “变量对象” 并将当前执行上下文与之关联 ,函数代码块中声明的 变量 和 函数 将作为属性添加到这个变量对象上。
有一点需要注意,只有函数声明(function declaration)会被加入到变量对象中,而函数表达式(function expression)会被忽略。
// 这种叫做函数声明,会被加入变量对象
function demo () {}
// tmp 是变量声明,也会被加入变量对象,但是作为一个函数表达式 demo2 不会被加入变量对象
var tmp = function demo2 () {}
全局执行上下文和函数执行上下文中的变量对象还略有不同,它们之间的差别简单来说:
- 全局上下文中的变量对象就是全局对象,以浏览器环境来说,就是
window对象。 - 函数执行上下文中的变量对象内部定义的属性,是不能被直接访问的,只有当函数被调用时,变量对象(
VO)被激活为活动对象(AO)时,我们才能访问到其中的属性和方法。
# 活动对象(activation object 简称 AO)
函数进入执行阶段时,原本不能访问的变量对象被激活成为一个活动对象,自此,我们可以访问到其中的各种属性。
其实变量对象和活动对象是一个东西,只不过处于不同的状态和阶段而已。
# 作用域链(scope chain)
作用域 规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做 作用域链。
# 当前可执行代码块的调用者(this)
如果当前函数被作为对象方法调用或使用 bind call apply 等 API 进行委托调用,则将当前代码块的调用者信息(this value)存入当前执行上下文,否则默认为全局对象调用。
关于 this 的创建细节,有点烦,有兴趣的话可以进入 这个章节 学习。
# 执行上下文数据结构模拟
如果将上述一个完整的执行上下文使用代码形式表现出来的话,应该类似于下面这种:
executionContext:{
[variable object | activation object]:{
